/************************************************************************************
FileName : cgi_impl.c
Description : Implementation for Cgi Interface SDK. Using cgi.h as the exported interface
************************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/prctl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdint.h>
#include <signal.h>
#include <errno.h>

#include "cgi_impl.h"
#include "cgi_common.h"
#include "crc32.h"

#define STOP_FLAG_NONSTOP		0
#define STOP_FLAG_STOP			1
#define ACCEPT_APPEND_LENGTH	100
#define MAX_CGI_CMD_COUNT		1000

pthread_t last_process_th;
pthread_t cgi_intf_thread;

int cgi_stop_flag = STOP_FLAG_NONSTOP;
pthread_mutex_t stop_flag_mutex;

int cgi_daemon_working_flag = 0;
int cgi_daemon_inited = 0;

typedef struct cgi_sock_list_node {
	int sock;
	struct cgi_sock_list_node * next;
	struct cgi_sock_list_node * previous;
} cgi_sock_node;

cgi_sock_node * sock_list_head;
cgi_sock_node * current_node;
int cgi_list_len = 0;
pthread_mutex_t sock_list_mutex;

process_cgi_cmd g_process_cgi_cmd_func = NULL;
pthread_mutex_t process_func_mutex;

int g_cgi_port = CGI_INTF_PORT;
pthread_mutex_t cgi_port_mutex;

static void set_cgi_stop_flag(int flag)
{
	pthread_mutex_lock(&stop_flag_mutex);
	cgi_stop_flag = flag;
	pthread_mutex_unlock(&stop_flag_mutex);
}

static int get_cgi_stop_flag()
{
	int flag = 0;
	pthread_mutex_lock(&stop_flag_mutex);
	flag = cgi_stop_flag;
	pthread_mutex_unlock(&stop_flag_mutex);

	return flag;
}

static int pop_cgi_sock_node(cgi_sock_node ** ppNode);

static int push_cgi_sock_node(int sock_fd)
{
	cgi_sock_node * p_node = NULL;
	int len = 0;

	pthread_mutex_lock(&sock_list_mutex);
	if (current_node == NULL || sock_list_head == NULL)
	{
		if (sock_list_head != NULL)
		{
			//free the sock_list_head to avoid memory leak
			free(sock_list_head);
			sock_list_head = NULL;
		}
		sock_list_head = (cgi_sock_node *)malloc(sizeof(cgi_sock_node));
		if (sock_list_head == NULL)
		{
			return 0;
		}
		p_node = sock_list_head;
		if (current_node != NULL)
		{
			//free the current_node to avoid memory leak
			free(current_node);
			current_node = NULL;
		}
		p_node->previous = NULL;
	}
	else
	{
		current_node->next = (cgi_sock_node *)malloc(sizeof(cgi_sock_node));
		if (current_node->next == NULL)
		{
			return 0;
		}
		p_node = current_node->next;
		p_node->previous = current_node;
	}

	p_node->next = NULL;
	p_node->sock = sock_fd;
	current_node = p_node;
	cgi_list_len++;
	len = cgi_list_len;
	
	pthread_mutex_unlock(&sock_list_mutex);

	if (len > MAX_CGI_CMD_COUNT)
	{
		// Too much command node in the list, for the sake of memory, throw some
		cgi_sock_node * poped_node = NULL;
		int ret = pop_cgi_sock_node(&poped_node);
		if (ret > 0)
		{
			close(poped_node->sock);
			//_my_assert((char*)__func__, __LINE__, poped_node->sock);
			free(poped_node);
			poped_node = NULL;
		} 
		else
		{
			printf("Pop sock node failed, about to return this thread!");
		}
	}
	
	return 1;
}

static int pop_cgi_sock_node(cgi_sock_node ** ppNode)
{
	cgi_sock_node * p_node = NULL;

	pthread_mutex_lock(&sock_list_mutex);

	if (sock_list_head == NULL)
	{
		//Empty list
		printf("Nothing in the list!\n");
		return 0;
	}
	p_node = sock_list_head;
	sock_list_head = p_node->next;
	if (sock_list_head == NULL)
	{
		//Empty after pop
		current_node = sock_list_head;
	}
	else
	{
		sock_list_head->previous = NULL;
	}
	*ppNode = p_node;
	cgi_list_len--;
	pthread_mutex_unlock(&sock_list_mutex);

	return 1;
}

static int get_buf_cmd_cgi(unsigned char * buf, int buf_len, unsigned short * p_cmd, unsigned short * p_len, unsigned char * version)
{
	if (buf == NULL || p_cmd == NULL || buf_len < CGI_PACK_LEAST_LEN)
	{
		printf("[CGI] Error in passing params!\n");
		return -1;
	}
	if (buf[0] != 'C' || buf[1] != 'G' || buf[2] != 'I')
	{
		printf("[CGI] Wrong head!\n");
		return -1;
	}
	*version = buf[3];
	if (*version > CGI_PACK_VERSION)
	{
		printf("[CGI] Wrong version number, version 0x%2X\n", *version);
		return -1;
	}
	
	*p_len = (unsigned short)buf[5] + ((unsigned short)buf[4] << 8);
	if (*p_len != buf_len)
	{
		printf("[CGI] Wrong pack len!received:%d bytes, but %d said in stream\n", buf_len, *p_len);
		return -1;
	}
	unsigned char crc[4] = {0};
	crc32_calc((char *)buf, buf_len, (char *)crc);
	if (crc[0] != 0 || crc[1] != 0 || crc[2] != 0 || crc[3] != 0)
	{
		printf("[CGI] crc check error %d, %d, %d, %d\n", crc[0], crc[1], crc[2], crc[3]);
		printf("[CGI] buf_len %d\n", buf_len);
		printf("[CGI] %2X, %2X, %2X, %2X, %2X, %2X, %2X, %2X, %2X, %2X, %2X, %2X\n", 
			buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);

		return -1;
	}
	*p_cmd = (unsigned short)buf[7] + ((unsigned short)buf[6] << 8);
	return 0;
}

static void handle_pipe(int sig)
{
	//do nothing
	printf("Catch SIGPIPI Signal,Ignore this\n");
}

void * cgi_process_thread_proc_impl(void * arg)
{
	pthread_detach(pthread_self());	
    prctl(PR_SET_NAME,"cgi_proc",0,0,0);

	int sock = 0;
	int ret = 0;
	char buf[CGI_PACK_MAX_LEN];
	char snd_cmd[CGI_PACK_LEAST_LEN];
	unsigned short cmd = 0;
	unsigned short len = 0;
	unsigned char version = 0;

	memset(snd_cmd, '\0', CGI_PACK_LEAST_LEN);
	snd_cmd[0] = CGI_PACK_HEAD_1;
	snd_cmd[1] = CGI_PACK_HEAD_2;
	snd_cmd[2] = CGI_PACK_HEAD_3;
	snd_cmd[3] = CGI_PACK_VERSION;

	struct sigaction sa;
	sa.sa_handler = handle_pipe;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGPIPE, &sa, NULL);

	cgi_sock_node * p_node = NULL;
	ret = pop_cgi_sock_node(&p_node);
	if (ret > 0)
	{
		sock = p_node->sock;
		free(p_node);
		p_node = NULL;
	} 
	else
	{
		printf("Pop sock node failed, about to return this thread!");
		return NULL;
	}
	
	while (1)
	{
		memset(buf, '\0', CGI_PACK_MAX_LEN);
		ret = recv(sock, buf, CGI_PACK_MAX_LEN, MSG_DONTWAIT | MSG_WAITALL);
		if (ret <= 0)
		{
			if (ret < 0 && errno == EWOULDBLOCK)
			{
				usleep(10 * 1000);
				continue;
			}

			if (ret == 0)
			{
				usleep(10 * 100);
				continue;
			}
			printf("[CGI] Socket error in cgi channel. About to return. Errno: %d\n", errno);
			snd_cmd[4] = (CGI_PACK_LEAST_LEN & 0xFF00) >> 8;
			snd_cmd[5] = CGI_PACK_LEAST_LEN & 0x00FF;
			snd_cmd[6] = (CGI_CMD_SOCK_ERROR & 0xFF00) >> 8;
			snd_cmd[7] = CGI_CMD_SOCK_ERROR & 0x00FF;
			crc32_calc(snd_cmd, CGI_PACK_LEAST_LEN - 4, &(snd_cmd[CGI_PACK_LEAST_LEN - 4]));
			if ( CGI_PACK_LEAST_LEN != send(sock, snd_cmd, CGI_PACK_LEAST_LEN, 0) )
			{
				printf("[CGI] Send data failed!\n");
			}
			close(sock);
			return NULL;
		} 
		else
		{
			if ( ret < CGI_PACK_LEAST_LEN )
			{
				ret += recv(sock, buf + ret, CGI_PACK_MAX_LEN - ret, MSG_WAITALL);
			}

			ret = get_buf_cmd_cgi((unsigned char *)buf, ret, &cmd, &len, &version);
			if (-1 == ret)
			{
				continue;
			}
			//call registered handle function here to process received command
			pthread_mutex_lock(&process_func_mutex);
			process_cgi_cmd process_cgi_cmd_func = g_process_cgi_cmd_func;
			pthread_mutex_unlock(&process_func_mutex);

			if (process_cgi_cmd_func != NULL)
			{
				process_cgi_cmd_func((unsigned char *)buf, len, sock, cmd);
			}
			else
			{
				int ret_len = CGI_PACK_LEAST_LEN;
				snd_cmd[4] = (ret_len & 0xFF00) >> 8;
				snd_cmd[5] = ret_len & 0x00FF;
				snd_cmd[6] = (CGI_CMD_NOT_SUPPORT & 0xFF00) >> 8;
				snd_cmd[7] = CGI_CMD_NOT_SUPPORT & 0x00FF;
				crc32_calc(snd_cmd, ret_len - 4, &(snd_cmd[ret_len - 4]));
				if ( ret_len != send(sock, snd_cmd, ret_len, 0) )
				{
					printf("[CGI] Send data failed!\n");
				}
			}

			break;
		}
	}
	close(sock);
	return NULL;
}

void * cgi_intf_thread_proc_impl(void * arg)
{
	int ret = 0;
	pthread_detach(pthread_self());	
    prctl(PR_SET_NAME,"cgi_intf",0,0,0);
	//TODO: Add more here!
	int socket_fd = 0;							//listen socket fd
	int accept_socket_fd = 0;					//accepted socket fd

	socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (socket_fd < 0)
	{
		printf("create cgi thread listen socket failed the errno is %d\n", errno);
		return NULL;
	}

	int on = 1;
	ret = setsockopt( socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
	if (ret == -1)
	{
		printf("Set sock opt SO_REUSEADDR error, errno is :%d.\n", errno); 
	}

	//local addr
	struct sockaddr_in send_addr;
	bzero(&send_addr, sizeof(send_addr));
	send_addr.sin_family = AF_INET;
	pthread_mutex_lock(&cgi_port_mutex);
	send_addr.sin_port = htons(g_cgi_port);
	pthread_mutex_unlock(&cgi_port_mutex);
	send_addr.sin_addr.s_addr = INADDR_ANY;

	if(bind(socket_fd, (struct sockaddr *)&send_addr, sizeof(send_addr)) == -1) 
	{
		printf( "bind local address error, errno is :%d.\n", errno); 
		close(socket_fd);
		return NULL;
	}

	if(listen(socket_fd, ACCEPT_APPEND_LENGTH) == -1)			// We give 
	{
		printf("listen local socket error!\n");
		close(socket_fd);
		return NULL;
	}
	struct sockaddr_in client_addr;
	int addr_size = 0;
	addr_size = sizeof(struct sockaddr_in);
	current_node = NULL;
	sock_list_head = NULL;
	pthread_mutex_init(&sock_list_mutex, NULL);
	while(1)
	{
		if (STOP_FLAG_STOP == get_cgi_stop_flag())
		{
			break;
		}
		//TODO: should be changed to non-block mode here to have chance to stop
		if((accept_socket_fd = accept(socket_fd, (struct sockaddr *)&client_addr, (socklen_t *)&addr_size)) == -1)
		{
			printf("accept local socket error!\n");
			usleep(1000 * 1000);
			continue;
		}
		ret = push_cgi_sock_node(accept_socket_fd);
		if (ret == 0)
		{
			printf("[CGI]Failed to push sock node.\n");
			close(accept_socket_fd);
			continue;
		}		

		ret = pthread_create(&last_process_th, NULL, cgi_process_thread_proc_impl, NULL);
		if(ret != 0)
		{
			printf("Failed to create CGI process thread.\n");
			continue;
		}
	}
	return NULL;
}

/*
 * Interface function: start_cgi_daemon
 * Description:	Set callback routine for the process of received CGI commands
 ****************************************************************************
 * @Param: 
 * @Return: 0 if success, otherwise error code 
 */
int start_cgi_daemon(process_cgi_cmd p_func, int cgi_port)
{
	if (0 != cgi_daemon_working_flag)
	{
		// Means this daemon is already working
		printf("CGI daemon already working.\n");
		return CGI_ALREADY_WORKING;
	}
	gen_crc32_table();
	//TODO: hook callback routine here
	if (0 == cgi_daemon_inited)
	{
		pthread_mutex_init(&process_func_mutex, NULL);
		pthread_mutex_init(&stop_flag_mutex, NULL);
		pthread_mutex_init(&cgi_port_mutex, NULL);
		cgi_daemon_inited = 1;
	}
	if (p_func != NULL)
	{
		pthread_mutex_lock(&process_func_mutex);
		g_process_cgi_cmd_func = p_func;
		pthread_mutex_unlock(&process_func_mutex);
	}
	else
	{
		return CGI_WRONG_PARAM;
	}
	if (cgi_port != USE_DEFAULT_CGI_PORT)
	{
		pthread_mutex_lock(&cgi_port_mutex);
		g_cgi_port = cgi_port;
		pthread_mutex_unlock(&cgi_port_mutex);
	}
	set_cgi_stop_flag(STOP_FLAG_NONSTOP);
	int ret = pthread_create(&cgi_intf_thread, NULL,(void *) cgi_intf_thread_proc_impl, NULL);
	if(ret != 0)
	{
		printf("Failed to create CGI interface thread.\n");
		ret = CGI_ERROR_THD;
	}
	else
	{
		printf("Succeed to create CGI interface thread!\n");
		cgi_daemon_working_flag = 1;
		ret = CGI_SUCCESS;
	}
	return ret;
}

/*
 * Interface function: stop_cgi_daemon
 * Description:	Stop the entire CGI daemon.
 ****************************************************************************
 * @Param: None
 * @Return: 0 if success, otherwise error code 
 */
int stop_cgi_daemon()
{
	int ret = CGI_SUCCESS;

	if (1 != cgi_daemon_working_flag)
	{
		// Means this daemon is already working
		printf("CGI daemon already working.\n");
		return CGI_NOT_WORKING;
	}
	else
	{
		set_cgi_stop_flag(STOP_FLAG_STOP);
		pthread_mutex_lock(&process_func_mutex);
		g_process_cgi_cmd_func = NULL;
		pthread_mutex_unlock(&process_func_mutex);
		//TODO: have to clean up the command list for memory recover
		cgi_daemon_working_flag = 0;
	}
	return ret;
}
